#include "bus_nathan.h"
#include <systemc.h>

NathanNamespaceBegin

//Author: Nathan Sandoval

using namespace std;

//SC_HAS_PROCESS(bus);

bus::bus(sc_module_name name) : sc_channel(name),  num_components(0)
{

    SC_THREAD(arbitrate);

}


bus::~bus()
{
//no pointers, nothing to do here
}

//main thread of bus
void bus::arbitrate()
{
    unsigned turnOrder = 0;
    bool allEmpty = true;
    while (1) {



        allEmpty = true; //assume all empty before verifying

        //check to see if there are any requests available
        for (int i = 0; i < masterIDs.size(); i++) {
            if (!requests[masterIDs[i]].empty()) {
                allEmpty = false;
                break;
            }
        }

        //if no requests, wait for one.
        if (allEmpty) {
            wait(masterRequest);
        }



        //pop a request from the correct vector
        if (!requests[masterIDs[turnOrder]].empty()) {
            ack = false; //set op about to be published to unacknowledged.
            currentLen = requests[masterIDs[turnOrder]].back();
            requests[masterIDs[turnOrder]].pop_back();
            currentOp = requests[masterIDs[turnOrder]].back();
            requests[masterIDs[turnOrder]].pop_back();
            currentAddr = requests[masterIDs[turnOrder]].back();
            requests[masterIDs[turnOrder]].pop_back();
            currentId = requests[masterIDs[turnOrder]].back();
            requests[masterIDs[turnOrder]].pop_back();
#ifdef DEBUG_BUS
            printf("\nPublishing request: %u %u %u %u\n", currentId, currentAddr, currentOp, currentLen);
#endif
            //tell the slave devices that there is a new request
            publishRequest.notify();
            int len = currentLen;// get a copy of current length, current length gets modified.
            wait(14, SC_NS, receivedCmd);// wait a limited time for acknowledge. in an untimed model, it doesn't matter how long we wait.
            //if using the approximate timed model, this waits 2 cycles, long enough for an acknowledge to arrive.

            cmdAck.notify();

	    
#ifdef DEBUG_BUS
	
	    printf("\nRequest %u %u %u %u is acknowledged? %d",currentId, currentAddr, currentOp, currentLen, ack); 
	    
#endif
	    
	    
	    
            //handle reading/writing for various lengths
            //bus watches to see when a transaction is done
            if (ack == true) {
                for (int i = 0 ; i < len; i++) {

                    //op == 0 means read, op == 1 means write
                    if (currentOp == 0)
                        wait(readData);
                    else if (currentOp == 1)
                        wait(receivedWriteData);

#ifdef DEBUG_BUS
                    printf("\nBus counting ops: %u out of %d of type %u\n", i+1,len,  currentOp);
#endif

                }// end wait for op to finish
            }
            
            else{
	      
	      wait(receivedRejection);
	      
	    }
            
            
        }

        turnOrder = (turnOrder + 1) % masterIDs.size();//increase the turn order to check for a reqest from the next Master (Round Robin stlye)
    }//end infinite loop
}

//implementation of bus master interface
void bus::Request(unsigned mst_id, unsigned addr, unsigned op, unsigned len) {

    //check to see if this master has made a request
    //if so, register it with the masterIDs vector to
    //include it in a round robin style request response
    //pattern.

#ifdef APPROXIMATE_TIMED
    wait(2*6.67, SC_NS); // one cycle wait to send a request
#endif


    int search;
    bool found = false;
    for (search = 0; search < masterIDs.size(); search++) {
        if (masterIDs[search] == mst_id) {
            found = true;
            break;
        }
    }

    if (!found) {
        masterIDs.push_back(mst_id);
        num_components++;
    }



#ifdef DEBUG_BUS
    printf("\nrequest: %u %u %u %u time: ", mst_id, addr, op, len);
    cout << sc_time_stamp() << endl;
#endif

    requests[mst_id].insert(requests[mst_id].begin(), len);
    requests[mst_id].insert(requests[mst_id].begin(), op);
    requests[mst_id].insert(requests[mst_id].begin(), addr);
    requests[mst_id].insert(requests[mst_id].begin(), mst_id);


    masterRequest.notify();


}

bool bus::WaitForAcknowledge(unsigned mst_id) {



#ifdef DEBUG_BUS
    printf("\nwait for ack: %u\n", mst_id);
#endif
    do {
        wait(cmdAck);
    } while (currentId != mst_id);

    
    if(!ack)
    receivedRejection.notify();

    return ack;

}

void bus::ReadData(unsigned &data) {




    wait(sentReadData);



    data = currentData;
#ifdef DEBUG_BUS
    printf("\nread data: %u\n", data);
#endif
    readData.notify(SC_ZERO_TIME);



}

void bus::WriteData(unsigned data) {


#ifdef DEBUG_BUS
    printf("\nwriting data: %u\n", data);
#endif
    currentData = data;
    wroteData.notify(SC_ZERO_TIME);



    if (currentLen > 1) {
        wait(receivedWriteData);
        currentLen--;

    }
}

//implementation of bus slave interface
//slave receives current op specs from the bus, will return ack if they belong to that component.
void bus::Listen(unsigned &req_addr, unsigned &req_op, unsigned &req_len) {



    wait(publishRequest);



    req_addr = currentAddr;
    req_op = currentOp;
    req_len = currentLen;
#ifdef DEBUG_BUS
    printf("\nListening: %u %u %u\n", req_addr, req_op, req_len);
#endif
}

void bus::Acknowledge() {

#ifdef APPROXIMATE_TIMED
    wait(6.67, SC_NS); // one cycle waiting for acknowledge
#endif


#ifdef DEBUG_BUS
    printf("\nAcknowledging request for %u %u %u %u\n", currentId, currentAddr, currentOp, currentLen);
#endif
    ack = true;
    receivedCmd.notify();




}

void bus::SendReadData(unsigned data) {

    //2 cycle read
#ifdef APPROXIMATE_TIMED
    wait(2*6.67, SC_NS);
#endif

#ifdef DEBUG_BUS
    printf("\nsend read data: %u\n", data);
#endif
    currentData = data;
    sentReadData.notify(SC_ZERO_TIME);

    if (currentLen > 1) {
        wait(readData);
        currentLen--;
    }

}

void bus::ReceiveWriteData(unsigned &data) {



    wait(wroteData);

#ifdef APPROXIMATE_TIMED
    wait(6.67,SC_NS); //wait 1 cycle for write
#endif


    data = currentData;


#ifdef DEBUG_BUS
    printf("\nreceived write data: %u\n", data);
#endif
    receivedWriteData.notify(SC_ZERO_TIME);



}



NathanNamespaceEnd

